/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.ui;

import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.prefs.Preferences;

import javax.swing.BoundedRangeModel;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

import org.apache.log4j.Logger;
import org.isqlviewer.sql.QueryMetadata;
import org.isqlviewer.sql.ResultSetRenderer;
import org.isqlviewer.swing.EnhancedTabbedPane;
import org.isqlviewer.swing.SwingUtilities;
import org.isqlviewer.swing.WizardPanel;
import org.isqlviewer.swing.action.SwingEventManager;
import org.isqlviewer.swing.table.EnhancedTableModel;
import org.isqlviewer.swing.table.ResultSetTableModel;
import org.isqlviewer.util.IsqlToolkit;
import org.isqlviewer.util.LocalMessages;

/**
 * Resultset handler that translate query results into tables within an EnhancedTabbedPane.
 * <p>
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class TabbedResultsetRenderer implements ResultSetRenderer {

    private static final long serialVersionUID = 1199835121337623550L;
    private static final String BUNDLE_NAME = "org.isqlviewer.ui.ResourceBundle";
    private LocalMessages messages = new LocalMessages(BUNDLE_NAME);

    private Preferences preferences = Preferences.userRoot().node(IsqlToolkit.getRootPreferencesNode());
    private MessageFormat choiceNodeFormat = new MessageFormat("string-substitutions/{0}");
    private AtomicBoolean isCancelled = new AtomicBoolean(false);

    private Properties currentSubstitutions = new Properties();
    private BusyQueryIndicator eyeCandy = null;
    private EnhancedTabbedPane tabbedPane = null;

    public TabbedResultsetRenderer(EnhancedTabbedPane container) {

        if (container == null) {
            throw new NullPointerException();
        }
        this.tabbedPane = container;
    }

    public void initialize() {

        synchronized (isCancelled) {
            isCancelled.set(false);
        }
        JPanel componentView = new JPanel();
        eyeCandy = new BusyQueryIndicator(isCancelled);
        eyeCandy.doLayout(componentView, null, null);
        int index = tabbedPane.getTabCount();
        tabbedPane.addTab("", SwingUtilities.loadIconResource("busy_animation", 16), componentView);
        tabbedPane.setClosableTab(index, false);
    }

    public void statementInitialized(String stmtID, Statement statement) {

        synchronized (isCancelled) {
            isCancelled.set(false);
        }
        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.statement_initalized", stmtID));
        eyeCandy.setStatement(statement);
    }

    public void handleSQLException(SQLException error) {

        ThrowableView view = new ThrowableView(error, "");
        JPanel parentComponent = new JPanel();
        view.doLayout(parentComponent, null, null);
        String tabText = "ERROR";
        Icon tabIcon = SwingUtilities.loadIconResource("fatal_error", 16);
        JComponent componentView = eyeCandy.getComponentView();
        int index = tabbedPane.indexOfComponent(componentView);
        if (index < 0) {
            tabbedPane.addTab("error", componentView);
            index = tabbedPane.indexOfComponent(componentView);
        }
        tabbedPane.setToolTipTextAt(index, "ERROR");
        tabbedPane.setTitleAt(index, tabText);
        tabbedPane.setComponentAt(index, parentComponent);
        tabbedPane.setClosableTab(index, true);
        tabbedPane.setIconAt(index, tabIcon);
        tabbedPane.setSelectedIndex(index);
    }

    public long processResultSet(String stmtID, ResultSet set, String nativeSQL, int count) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.process_resultset", stmtID));
        try {
            ResultSetTableModel model = new ResultSetTableModel();
            model.setResults(set, this);
            eyeCandy.setQueryData(model);
            return model.getTrueRowCount();
        } catch (SQLException error) {
            handleSQLException(error);
        }
        return -1;
    }

    public void recieveResultsetWarnings(ResultSet set, SQLWarning warnings) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.resultset_warnings"));
    }

    public void recieveStatementWarnings(Statement stmt, SQLWarning warnings) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.statement_warnings"));
    }

    public void processRowUpdates(String stmtID, int updateCount, String nativeSQL) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.row_updates", Integer.toString(updateCount)));
        Logger logger = IsqlToolkit.getApplicationLogger();
        logger.info(messages.format("tabbedresultsetrenderer.rows_affected", new Integer(updateCount)));
    }

    public void finalizeStatement(String stmtID, QueryMetadata metadata) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.finalize_statement", stmtID));

        String html = "";
        EnhancedTableModel resultSet = eyeCandy.getQueryData();
        JComponent componentView = eyeCandy.getComponentView();
        if (resultSet != null) {
            String str = "";
            Icon ico = null;
            str = "Resultset";
            ico = SwingUtilities.loadIconResource("table", 16);
            int index = tabbedPane.indexOfComponent(componentView);
            if (index < 0) {
                tabbedPane.addTab("", componentView);
                index = tabbedPane.indexOfComponent(componentView);
            }
            DataGrid dataGrid = new DataGrid();
            componentView.removeAll();
            dataGrid.doLayout(componentView, null, null);
            dataGrid.setModel(eyeCandy.getQueryData());
            dataGrid.setMetadata(metadata);
            componentView.putClientProperty("x", dataGrid);

            tabbedPane.setToolTipTextAt(index, html);
            tabbedPane.setTitleAt(index, str);
            tabbedPane.setComponentAt(index, componentView);
            tabbedPane.setClosableTab(index, true);
            tabbedPane.setIconAt(index, ico);
            tabbedPane.setSelectedIndex(index);
        } else {
            int index = tabbedPane.indexOfComponent(componentView);
            if (index >= 0) {
                tabbedPane.removeTabAt(index);
            }
        }
        tabbedPane.invalidate();
        eyeCandy.destroy();
    }

    public void processGeneratedKeys(String stmtID, ResultSet keys, String nativeSQL) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.processing_generated_keys", stmtID));
        processResultSet(stmtID, keys, nativeSQL, Integer.MIN_VALUE);
    }

    public boolean supportsGeneratedKeys() {

        return true;
    }

    public boolean supportsUpdateableResultSets() {

        return false;
    }

    public boolean isCancelled() {

        return isCancelled.get();
    }

    public String substituteVariable(String variableName) {

        eyeCandy.updateStatus(messages.format("TabbedResultsetRenderer.processing_variable_substitutions"));
        String normalized = variableName.toLowerCase().trim();
        normalized = normalized.replace('.', '-');
        String existingInput = currentSubstitutions.getProperty(normalized);
        if (existingInput != null) {
            return existingInput;
        }

        Object[] arguments = new Object[]{normalized};
        Preferences previousChoices = preferences.node(choiceNodeFormat.format(arguments));
        Frame frameOwner = (Frame) javax.swing.SwingUtilities.getAncestorOfClass(Frame.class, tabbedPane);
        VariableSubstitutor substitutor = new VariableSubstitutor(frameOwner, variableName, previousChoices);
        substitutor.setVisible(true);
        String selection = substitutor.getSubstitution();
        if (selection != null) {
            long existing = previousChoices.getLong(selection, 0);
            previousChoices.putLong(selection, existing++);
            currentSubstitutions.put(normalized, selection);
        }
        return selection;
    }

    public EnhancedTabbedPane getTabbedPane() {

        return tabbedPane;
    }

    private static class BusyQueryIndicator extends AbstractApplicationView implements ActionListener {

        private JProgressBar progressBar = null;
        private JButton cancelButton = null;
        private JLabel statusLabel = null;
        private AtomicBoolean isCancelled = null;
        private EnhancedTableModel queryData = null;
        private JComponent componentView = null;
        private Statement statement = null;
        private LocalMessages msgs = new LocalMessages(BUNDLE_NAME);

        public BusyQueryIndicator(AtomicBoolean isCancelled) {

            this.isCancelled = isCancelled;
        }

        public void destroy() {

            progressBar = null;
            cancelButton = null;
            statusLabel = null;
            isCancelled = null;
            queryData = null;
            componentView = null;
            statement = null;
        }

        public void setStatement(Statement statement) {

            this.statement = statement;
        }

        public void updateStatus(String text) {

            statusLabel.setText(text);
            BoundedRangeModel model = progressBar.getModel();
            model.setValue(model.getValue() + 1);
        }

        public void configureMenubar(JMenuBar menuBar) {

        }

        public void disposeView(Preferences preferences) {

        }

        public void doLayout(JComponent parentComponent, Preferences preferences, SwingEventManager eventManager) {

            Object obj = null;

            this.componentView = parentComponent;
            JPanel container = new JPanel(new GridBagLayout());
            parentComponent.setLayout(new WizardPanel.Layout());
            parentComponent.add(container);

            obj = constrain(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
            progressBar = new JProgressBar(JProgressBar.HORIZONTAL, 1, 6);
            container.add(progressBar, obj);

            statusLabel = new JLabel(SwingUtilities.loadIconResource("information", 16));
            obj = constrain(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
            container.add(statusLabel, obj);

            cancelButton = new JButton(msgs.format("TabbedResultsetRenderer.cancel_button.text"));
            cancelButton.setToolTipText(msgs.format("TabbedResultsetRenderer.cancel_button.tip"));
            cancelButton.addActionListener(this);
            obj = constrain(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
            container.add(cancelButton, obj);

            obj = constrain(1, 3, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL);
            container.add(Box.createVerticalGlue(), obj);

            obj = constrain(1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL);
            container.add(Box.createVerticalGlue(), obj);
        }

        public void initializeView() {

        }

        public void actionPerformed(ActionEvent e) {

            synchronized (isCancelled) {
                isCancelled.set(true);
                if (statement != null) {
                    try {
                        statement.cancel();
                    } catch (SQLException error) {
                        Logger logger = IsqlToolkit.getApplicationLogger();
                        String text = msgs.format("TabbedResultsetRenderer.statement_cancel_failed");
                        logger.warn(text, error);
                    }
                }
            }
            cancelButton.setEnabled(false);
        }

        public EnhancedTableModel getQueryData() {

            return queryData;
        }

        public void setQueryData(EnhancedTableModel queryData) {

            this.queryData = queryData;
        }

        public JComponent getComponentView() {

            return componentView;
        }
    }

}